#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <linux/netlink.h>
#include <linux/connector.h>
#define _LINUX_TIME_H
#include <linux/cn_proc.h>
#include "list.h"
#include "public_relate.h"
#include <syslog.h>

static int sd;
static struct sockaddr_nl daddr;
static struct nlmsghdr *nlhdr = NULL;
static char *procname;
/* change cn_proc to MODE */
void change_cn_proc_mode(int mode)
{
    int ret;
    struct cn_msg *cnmsg;
    struct iovec iov;
    struct msghdr msg;
     if (mode == PROC_CN_MCAST_LISTEN)
        syslog(LOG_USER|LOG_INFO, "%s: turnning on process event listening.\n", procname);
     else
        syslog(LOG_USER|LOG_INFO, "%s: turnning off process event listening.\n", procname);


    memset(nlhdr, 0, sizeof(NLMSG_SPACE(MAX_MSGSIZE)));
    memset(&iov, 0, sizeof(struct iovec));
    memset(&msg, 0, sizeof(struct msghdr));
    cnmsg = (struct cn_msg *) NLMSG_DATA(nlhdr);
    * (int *) cnmsg->data = mode;
    nlhdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct cn_msg) +
				    sizeof(enum proc_cn_mcast_op));
    nlhdr->nlmsg_pid = getpid();
    nlhdr->nlmsg_flags = 0;
    cnmsg->id.idx = CN_IDX_PROC;
    cnmsg->id.val = CN_VAL_PROC;
    cnmsg->seq = 0;
    cnmsg->ack = 0;
    cnmsg->len = sizeof(enum proc_cn_mcast_op);
    iov.iov_base = (void *) nlhdr;
    iov.iov_len = nlhdr->nlmsg_len;
    msg.msg_name = (void *) &daddr;
    msg.msg_namelen = sizeof(daddr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    ret = sendmsg(sd, &msg, 0);
    if (ret == -1)
		perror(procname);
}

/* just send message */
static int send_uc_message(struct user_message *uc, size_t size)
{
	int sd;
	struct sockaddr_nl daddr;
	int group;
	struct nlmsghdr *nlhdr = NULL;
	struct msghdr msg;
	struct iovec iov;
	struct cn_msg *cnmsg;
	int ret;
	uc->magic = RELATE_MAGIC;
	group = CN_IDX_RELATE;
	sd = socket(PF_NETLINK, SOCK_RAW, NETLINK_CONNECTOR);
	if (sd < 0)
	{
	        syslog(LOG_USER|LOG_INFO, "%s: socket open error\n", procname);
		goto out;
	}
	nlhdr = (struct nlmsghdr *) malloc (NLMSG_SPACE (MAX_MSGSIZE));
	if (nlhdr == NULL)
		goto out;
	daddr.nl_family = AF_NETLINK;
	daddr.nl_pid = 0;
	daddr.nl_groups = CN_IDX_RELATE;
	if (bind(sd, (struct sockaddr *) &daddr, sizeof (daddr)) < 0)
	{
	  syslog(LOG_USER|LOG_INFO, "%s: bind error\n", procname);
	  goto ret_free;
	}
	ret = setsockopt(sd, SOL_NETLINK,1, &group, sizeof(group));
	if (ret < 0)
	{
	  	syslog(LOG_USER|LOG_INFO, "%s: setsocket error\n", procname);
		goto ret_free;
	}
	memset (nlhdr, 0, sizeof (NLMSG_SPACE (MAX_MSGSIZE)));
	memset (&iov, 0, sizeof (struct iovec));
	memset (&msg, 0, sizeof (struct msghdr));
	cnmsg = (struct cn_msg *) NLMSG_DATA (nlhdr);
	memcpy(cnmsg->data, uc, size);
	nlhdr->nlmsg_len = NLMSG_LENGTH (sizeof (struct cn_msg) + size);
	nlhdr->nlmsg_pid = getpid();
	nlhdr->nlmsg_flags = 0;
	cnmsg->id.idx = CN_IDX_RELATE;
	cnmsg->id.val = CN_VAL_RELATE;
	cnmsg->seq = 0;
	cnmsg->ack = 0;
	cnmsg->len = size;	/* user_message length */
	iov.iov_base = (void *) nlhdr;
	iov.iov_len = nlhdr->nlmsg_len;
	msg.msg_name = (void *) &daddr;
	msg.msg_namelen = sizeof (daddr);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	ret = sendmsg(sd, &msg, 0);
	if (ret < 0)
	  syslog(LOG_USER|LOG_INFO,"%s: sendmsg error\n", procname);
ret_free:
	free(nlhdr);
out:
	close(sd);
	return 0;
}

/* nothing to translate ,just send this message */
static void translate_fork(int ppid, int cpid)
{
	RDprint("fork: p:%d c:%d\n", ppid, cpid);
	struct user_message uc;
	uc.what = KERN_FORK;
	uc.u.fork.ppid = ppid;
	uc.u.fork.cpid = cpid;
	send_uc_message(&uc, sizeof(uc));
}
/* get /proc/PID/exe to buf 
   /proc/PID/exe link to *NOTHING* means it's a kthread....
   return -1 to omit it
   */
static int get_pid_exe(int size, char *buf, pid_t pid)
{
	int i;
	char file[RELEN];
	snprintf(file, RELEN, "/proc/%d/exe", pid);
	i = readlink(file, buf, RELEN);
	if ( i < 0 )
		return -1;
	buf[i] = 0;
	return 0;
}

static void translate_exec(int pid)
{
	int ret;
	struct user_message uc;
	uc.what = KERN_EXEC;
	uc.u.exec.pid = pid;
	if ( (ret = get_pid_exe(PATHLEN, uc.u.exec.path, pid)) < 0)
	{
		RDprint("\t\tprocess exit so i returned\n");
		return ;
	}
	RDprint("what:%d pid:%d path:%s\n", uc.what, uc.u.exec.pid, uc.u.exec.path);
	send_uc_message(&uc, sizeof(uc));
}

static void translate_exit(int pid)
{
	RDprint("exit: %d \n\n", pid);
	struct user_message uc;
	uc.what = KERN_EXIT;
	uc.u.exit.pid = pid;
	send_uc_message(&uc, sizeof(uc));
}

/* CRTL + C sig handler */
static void sigint_handler(int signo)
{
    change_cn_proc_mode(PROC_CN_MCAST_IGNORE);
	if (nlhdr != NULL)
		free(nlhdr);
    close(sd);
	/* if this is exit(0), it will make a fatal error */
    _exit(0); 
}

static void sigchld_handler(int signo)
{
   syslog(LOG_USER|LOG_INFO, "%s: record child process exit!\n", procname);
   _exit(0);
}

static int get_listen_port();

//void relate_main(){};
//int daemon_main(int argc, char **argv)
int main(int argc, char **argv)
{
    int group = CN_IDX_RELATE;
	int pid;
    int ret;
    struct sigaction sigint_action;
    struct iovec iov;
    struct msghdr msg;
    struct proc_event *procevent;
    struct cn_msg *cnmsg;

    memset(&sigint_action, 0, sizeof(struct sigaction));
    sigint_action.sa_flags = SA_ONESHOT;
    sigint_action.sa_handler = &sigint_handler;
    sigaction(SIGTERM, &sigint_action, NULL);

    memset(&sigint_action, 0, sizeof(struct sigaction));
    sigint_action.sa_flags = SA_ONESHOT;
    sigint_action.sa_handler = &sigchld_handler;
    sigaction(SIGCHLD, &sigint_action, NULL);

    procname = argv[0];
    sd = socket(PF_NETLINK, SOCK_RAW, NETLINK_CONNECTOR);
	if (sd < 0)
	{
	        syslog(LOG_USER|LOG_INFO, "%s: socket open error\n", procname);
		goto err_closed;
	}
    nlhdr = (struct nlmsghdr *) malloc(NLMSG_SPACE(MAX_MSGSIZE));
    if (nlhdr == NULL)
		goto out_free;
    daddr.nl_family = AF_NETLINK;
    daddr.nl_pid = 0;
    daddr.nl_groups = CN_IDX_PROC;
    if (bind(sd, (struct sockaddr *) &daddr, sizeof(daddr)) == -1)
		goto err_closed;
	ret = setsockopt(sd, SOL_NETLINK,1, &group, sizeof(group));
	if (ret < 0)
	{
	        syslog(LOG_USER|LOG_INFO, "%s: setsocketopt error\n", procname);
			goto err_closed;
	}
	/* change cn_proc to listen mode */
	// how to change cn_proc to ignore when I exited unexpected??
    change_cn_proc_mode(PROC_CN_MCAST_LISTEN);
	if ( (ret =  get_listen_port()) < 0)
	{
	    syslog(LOG_USER|LOG_INFO, "%s: setsocketopt error\n", procname);
		goto err_closed;
	}

	daemon(1,0);
	/* fork a child to exec sched_relate*/
	if((pid=fork()) == 0) relate_main();

    while (1) {
		memset(nlhdr, 0, NLMSG_SPACE(MAX_MSGSIZE));
		memset(&iov, 0, sizeof(struct iovec));
		memset(&msg, 0, sizeof(struct msghdr));
		iov.iov_base = (void *) nlhdr;
		iov.iov_len = NLMSG_SPACE(MAX_MSGSIZE);
		msg.msg_name = (void *) &daddr;
		msg.msg_namelen = sizeof(daddr);
		msg.msg_iov = &iov;
		msg.msg_iovlen = 1;
		ret = recvmsg(sd, &msg, 0);
		if (ret == 0) {
			syslog(LOG_USER|LOG_INFO, "%s: Exit\n", procname);
			exit(0);
		} else if (ret == -1) {
/* interrupted by signal.... */
			continue;
		} else {
			cnmsg = (struct cn_msg *) NLMSG_DATA(nlhdr);
			procevent = (struct proc_event *) cnmsg->data;
			switch (procevent->what) {
				/* get parent and child's pid */
				case PROC_EVENT_FORK:
				translate_fork(procevent->event_data.fork.parent_pid,
					   procevent->event_data.fork.child_pid);
				break;
				/* get child pid, trans to name */
				case PROC_EVENT_EXEC:
				translate_exec(procevent->event_data.exec.process_pid);
				break;
				/* get process pid */
				case PROC_EVENT_EXIT:
				translate_exit(procevent->event_data.exit.process_pid);
				break;
				case PROC_EVENT_NONE:
				syslog(LOG_USER|LOG_INFO,"%s: Unkown process action: %x\n", procname, procevent->what);
                break;
                
                /* listen */
#ifndef PROC_EVENT_LISTEN
#define PROC_EVENT_LISTEN 1111
#endif
                case PROC_EVENT_LISTEN:
				syslog(LOG_USER|LOG_INFO,"%s: process event: acknowledge for turning on process event listening\n", procname);
				break;
				/* ignore */
#ifndef PROC_EVENT_IGNORE
#define PROC_EVENT_IGNORE 1112
#endif
                case PROC_EVENT_IGNORE:
				syslog(LOG_USER|LOG_INFO,"%s: process event: acknowledge for turning off process event listening\n", procname);
                break;

				case PROC_EVENT_GID:
				case PROC_EVENT_UID:
				break;
				default:
				syslog(LOG_USER|LOG_INFO,"%s: Unkown process action: %x\n", procname, procevent->what);
				break;
			}
		}
    }
out_free:
	free(nlhdr);
err_closed:
	close(sd);
	exit(0);
}
